using System.Collections.Generic;
using System.Data;
using Tycho.Parser.Peg;

namespace Tycho.Grammar {
    class GrammarProductionCompiler : IProductionCompiler {
        private readonly IProductionCompiler ProductionCompiler;

        public GrammarProductionCompiler (IProductionCompiler productionCompiler) {
            ProductionCompiler = productionCompiler;
        }

        public GrammarTree Compile (ITerm term, ChoicePrecedence precedence, ProductionCompilerContext parentContext) {
            var context = parentContext.Clone ();
            var lookup = new ProductionLookup (parentContext.Lookup);
            context.Lookup = lookup;

            return CompileProductions (term, context, lookup);
        }

        private GrammarTree CompileProductions (ITerm term, ProductionCompilerContext context, ProductionLookup lookup) {
            var subGrammars = new Dictionary<string, GrammarTree> ();

            IList<ITerm> namedProductions = GetNamedProductions (term);
            IProduction lastProduction = CompileNamedProductions (namedProductions, context, lookup, subGrammars);

            if (lastProduction == null) {
                throw new GrammarException ();
            } else {
                return new GrammarTree (lastProduction, subGrammars);
            }
        }

        private static IList<ITerm> GetNamedProductions (ITerm term) {
            var compositeTerm = (CompositeTerm) term;
            return ((ListTerm) compositeTerm.SubTerms["productions"]).Terms;
        }

        private IProduction CompileNamedProductions (IEnumerable<ITerm> namedProductions, ProductionCompilerContext context, ProductionLookup lookup, IDictionary<string, GrammarTree> subGrammars) {
            IProduction lastProduction = null;

            foreach (var namedProductionTerm in namedProductions) {
                if (IsNamedProduction (namedProductionTerm)) {
                    lastProduction = AddNamedProduction ((CompositeTerm) namedProductionTerm, context, lookup, subGrammars);
                } else {
                    lastProduction = ProductionCompiler.Compile (namedProductionTerm, null, context).Production;
                }
            }

            return lastProduction;
        }

        private IProduction AddNamedProduction (CompositeTerm namedProduction, ProductionCompilerContext context, ProductionLookup lookup, IDictionary<string, GrammarTree> subGrammars) {
            GrammarTree subTree = CompileNamedProduction (namedProduction, context);
            string subTreeName = GetProductionName (namedProduction);

            lookup.Add (subTreeName, subTree.Production);
            subGrammars.Add (subTreeName, subTree);

            return subTree.Production;
        }

        private GrammarTree CompileNamedProduction (CompositeTerm namedProduction, ProductionCompilerContext context) {
            ITerm productionTerm = namedProduction.SubTerms["production"];
            return ProductionCompiler.Compile (productionTerm, null, context);
        }

        private static string GetProductionName (CompositeTerm namedProduction) {
            ITerm nameTerm = namedProduction.SubTerms["name"];
            return ((IdentifierTerm) nameTerm).Name;
        }

        private static bool IsNamedProduction (ITerm production) {
            return (production is CompositeTerm
                    && ((CompositeTerm) production).Name == "named-production");
        }
    }
}